package tecpointbenchmark.view;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.event.*;
import java.util.ArrayList;
import java.util.Observable;
import java.util.Observer;

import javax.swing.*;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;

import tecpointbenchmark.controller.Controller;
import tecpointbenchmark.model.dto.ModuleInfoDTO;
import tecpointbenchmark.model.dto.ResultDTO;

/**
 * The GUI for the Benchmarking program
 * 
 * @author Elena
 */
public class GUI extends JFrame implements Observer {
	// Frame
	private static final long serialVersionUID = 478639016562607148L;
	private Controller cont;
	private int frameWidth = 500;
	private int frameHeight = 520;
	private String title = "Benchmark program";
	private JLabel labelTitle = new JLabel(title);

	// Menus
	private JMenuBar menuBar = new JMenuBar();
	private JMenu fileMenu = new JMenu("File");
	private JMenu helpMenu = new JMenu("Help");
	private JMenuItem exit = new JMenuItem("Exit", KeyEvent.VK_E);
	private JMenuItem help = new JMenuItem("Help", KeyEvent.VK_H);
	private JMenuItem about = new JMenuItem("About", KeyEvent.VK_A);

	// Panels
	private JPanel panelTop = new JPanel();
	private JPanel panelBottom = new JPanel();

	// Textareas
	private JTextArea resultBox = new JTextArea();

	// Labels
	private JLabel labelModules = new JLabel("Modules:");
	private JLabel labelAddModules = new JLabel("Added modules:");
	private JLabel labelResults = new JLabel("Results / Info:");

	// Buttons
	private JButton buttonAdd = new JButton("Add");
	private JButton buttonDel = new JButton("Delete");
	private JButton buttonStart = new JButton("Start");
	private JButton buttonStop = new JButton("Stop");

	// Models
	private DefaultListModel modelModules = new DefaultListModel();
	private DefaultListModel modelAddModules = new DefaultListModel();

	// Lists
	// The list if all modules - The left box.
	private ArrayList<ModuleInfoDTO> modules = new ArrayList<ModuleInfoDTO>();

	// The list of the moduels in the list "listAddModules" - The right box.
	private ArrayList<ModuleInfoDTO> modulesAdd = new ArrayList<ModuleInfoDTO>();

	// The selected/marked modules in the list "listModules".
	private ArrayList<String> selectedAdd = new ArrayList<String>();

	// The selected/marked modules in the list "listAddModules".
	private ArrayList<String> selectedDel = new ArrayList<String>();

	// The list of the modules
	private JList listModules = new JList(modelModules);
	private JList listAddModules = new JList(modelAddModules);

	// Result-related Vars
	private ArrayList<ResultDTO> results = new ArrayList<ResultDTO>();
	private boolean moduleReady = false;
	private Thread starterThread;

	// Stop-related vars
	private boolean continuing = true;

	/**
	 * Creates a new GUI for the controller <code>View</code>.
	 * 
	 * @param The
	 *            controller cont which View will be using.
	 */
	public GUI(Controller cont) {
		this.cont = cont;
		starterThread = new Starter(this);
		createGraphic();
	}

	/**
	 * Nested class handling the starting of the benchmark modules
	 * 
	 * @author Fredrik
	 * 
	 */
	private static class Starter extends Thread {
		GUI gui;

		public Starter(GUI gui) {
			this.gui = gui;
		}

		@Override
		public void run() {
			gui.startTest();
		}
	}

	/**
	 * Creates the graphic for the program.
	 */
	private void createGraphic() {
		// Frame
		setSize(frameWidth, frameHeight);
		setTitle(labelTitle.getText());
		setResizable(false);
		setLayout(new BorderLayout());
		createTestPanel();

		// Menu
		fileMenu.setMnemonic(KeyEvent.VK_F);

		// Menu Exit.
		exit.addActionListener(new ActionListener() {
			/**
			 * An event called by the Exit menu item. Exits the program.
			 * 
			 * @param e
			 *            The event "Exit".
			 */
			public void actionPerformed(ActionEvent e) {
				System.exit(0);
			}
		});

		fileMenu.add(exit);

		// Menu choose test.
		about.addActionListener(new ActionListener() {

			/**
			 * An event called by the about menu item.
			 * 
			 * @param e
			 *            The event "About".
			 */
			public void actionPerformed(ActionEvent e) {
				JOptionPane.showMessageDialog(null, title
						+ "\n(C) 2011 TecPoint", title,
						JOptionPane.INFORMATION_MESSAGE);
			}
		});
		helpMenu.add(about);

		// Menu choose test.
		help.addActionListener(new ActionListener() {

			/**
			 * An event called by the help menu item.
			 * 
			 * @param e
			 *            The event "Help".
			 */
			public void actionPerformed(ActionEvent e) {
				JOptionPane
						.showMessageDialog(
								null,
								"To start the benchmark select "
										+ "the components that \nyou want to test in the left optionbox "
										+ "and click add. \nWhen you have items in the right optionbox "
										+ "click \nthe start-button to start them, or add more items from "
										+ "the left box. \nIf you don't want a selected module to run anymore "
										+ ", and haven't \nstarted it yet, you can mark it in the right option "
										+ "box and choose delete. "
										+ "\nTo cancel the runnning tests click the stop-button. "
										+ "\nTo exit the program use File->Exit or use the X in the upper "
										+ "right corner.", "Help",
								JOptionPane.INFORMATION_MESSAGE);
			}
		});
		helpMenu.add(help);

		// Exit-cross
		addWindowListener(new WindowAdapter() {

			/**
			 * An event called by exit-cross item. Exits the program.
			 * 
			 * @param actionEvent
			 *            The event.
			 */
			@Override
			public void windowClosing(WindowEvent e) {
				System.exit(0);
			}
		});

		menuBar.add(fileMenu);
		menuBar.add(helpMenu);
		setJMenuBar(menuBar);
		setLocationRelativeTo(null); // Center window.
		setVisible(true);
	}

	private void createTestPanel() {
		// Panel for the top
		GroupLayout layoutTop = new GroupLayout(panelTop);
		panelTop.setLayout(layoutTop);
		panelTop.setPreferredSize(new Dimension(frameWidth, frameHeight / 3));
		panelTop.setBackground(Color.white);

		// Panel for the bottom
		GroupLayout layoutBottom = new GroupLayout(panelBottom);
		panelBottom.setLayout(layoutBottom);
		panelBottom
				.setPreferredSize(new Dimension(frameWidth, frameHeight / 3));
		panelBottom.setBackground(Color.white);

		// Result/info box
		resultBox.setEditable(false);
		resultBox.setLineWrap(true);
		resultBox.setWrapStyleWord(true);

		// Sets the add- and delete button to disable.
		buttonAdd.setEnabled(false);
		buttonDel.setEnabled(false);
		buttonStart.setEnabled(false);
		buttonStop.setEnabled(false);

		// Adds a list selection listener to the list of modules.
		listModules.addListSelectionListener(new ListSelectionListener() {

			/**
			 * An event called by the list of modules.
			 * 
			 * @param e
			 *            The selection event.
			 */
			@Override
			public void valueChanged(ListSelectionEvent e) {

				if (e.getValueIsAdjusting() == false) {
					JList list = (JList) e.getSource();
					Object[] sel = list.getSelectedValues();

					selectedAdd.clear();
					resultBox.setText("");

					if (list.getSelectedIndex() == -1) {
						// No selection, disable add button.
						buttonAdd.setEnabled(false);
					} else {
						// Selection, enable the add button.
						buttonAdd.setEnabled(true);

						// Get all selected items
						for (int i = 0; i < sel.length; i++) {
							selectedAdd.add(sel[i].toString());
						}
					}
					// The info-box shows info about the selected modules.
					for (int i = 0; i < sel.length; i++) {
						if ("All".equals(sel[i].toString())) {
							for (int k = 0; k < modules.size(); k++) {
								resultBox.append(modules.get(k).getName()
										+ ":\n" + modules.get(k).getShortdesc()
										+ "\n\n");
							}
						} else {
							for (int j = 0; j < modules.size(); j++) {

								if (modules.get(j).getName().equals(
										sel[i].toString())) {
									resultBox.append(modules.get(j).getName()
											+ ":\n"
											+ modules.get(j).getLongdesc()
											+ "\n\n");
								}
							}
						}
					}
				}
			}
		});

		// Adds a list selection listener to the list of added modules.
		listAddModules.addListSelectionListener(new ListSelectionListener() {

			/**
			 * An event called by the list of added modules.
			 * 
			 * @param e
			 *            The selection event.
			 */
			@Override
			public void valueChanged(ListSelectionEvent e) {

				if (e.getValueIsAdjusting() == false) {
					JList list = (JList) e.getSource();
					if (list.getSelectedIndex() == -1) {
						// No selection, disable delete button.
						buttonDel.setEnabled(false);
					} else {
						// Selection, enable the delete button.
						buttonDel.setEnabled(true);

						// Get all selected items
						Object[] sel = list.getSelectedValues();
						selectedDel.clear();
						for (int i = 0; i < sel.length; i++) {
							selectedDel.add(sel[i].toString());
							for (int j = 0; j < modules.size(); j++) {
								if (sel[i].toString().equals(
										modules.get(j).getName())) {
									modulesAdd.add(modules.get(j));
								}
							}
						}
					}
				}
			}
		});

		// Gets the modules names
		for (int i = 0; i < cont.getModules().size(); i++) {
			modules.add(cont.getModules().get(i));
		}

		// Initialize the list with the modules
		for (int i = 0; i < modules.size(); i++) {
			modelModules.add(i, modules.get(i).getName());
		}

		// Adds a scroll to the lists, only shows if the list is not big enough
		JScrollPane scrollModules = new JScrollPane(listModules);
		scrollModules.setPreferredSize(new Dimension(195, 130));
		JScrollPane scrollAddModules = new JScrollPane(listAddModules);
		scrollAddModules.setPreferredSize(new Dimension(195, 130));

		// Gaps between the components
		layoutTop.setAutoCreateGaps(true);
		layoutTop.setAutoCreateContainerGaps(true);

		// Add button and Delete button the same size.
		layoutTop.linkSize(SwingConstants.HORIZONTAL, buttonAdd, buttonDel);

		// Sets the layout for the listbox of modules.
		// Horizontal.
		layoutTop.setHorizontalGroup(layoutTop.createSequentialGroup()
				.addGroup(
						layoutTop.createParallelGroup(
								GroupLayout.Alignment.LEADING).addComponent(
								labelModules).addComponent(scrollModules,
								GroupLayout.PREFERRED_SIZE,
								GroupLayout.DEFAULT_SIZE,
								GroupLayout.PREFERRED_SIZE)).addGroup(
						layoutTop.createParallelGroup(
								GroupLayout.Alignment.LEADING).addComponent(
								buttonAdd).addComponent(buttonDel)).addGroup(
						layoutTop.createParallelGroup(
								GroupLayout.Alignment.LEADING).addComponent(
								labelAddModules).addComponent(scrollAddModules,
								GroupLayout.PREFERRED_SIZE,
								GroupLayout.DEFAULT_SIZE,
								GroupLayout.PREFERRED_SIZE)));
		// Vertical.
		layoutTop.setVerticalGroup(layoutTop.createSequentialGroup().addGroup(
				layoutTop.createParallelGroup(GroupLayout.Alignment.LEADING)
						.addComponent(labelModules).addComponent(
								labelAddModules)).addGroup(
				layoutTop.createParallelGroup(GroupLayout.Alignment.LEADING)
						.addComponent(scrollModules,
								GroupLayout.PREFERRED_SIZE,
								GroupLayout.DEFAULT_SIZE,
								GroupLayout.PREFERRED_SIZE).addGroup(
								layoutTop.createSequentialGroup().addComponent(
										buttonAdd).addComponent(buttonDel))
						.addComponent(scrollAddModules,
								GroupLayout.PREFERRED_SIZE,
								GroupLayout.DEFAULT_SIZE,
								GroupLayout.PREFERRED_SIZE)));

		// The add button
		buttonAdd.addActionListener(new ActionListener() {

			/**
			 * An event called by add button. Adds a module to test on.
			 * 
			 * @param actionEvent
			 *            The event.
			 */
			public void actionPerformed(ActionEvent e) {

				for (int i = 0; i < selectedAdd.size(); i++) {
					for (int j = 0; j < modules.size(); j++) {
						if (selectedAdd.get(i).equals(modules.get(j).getName())) {
							modulesAdd.add(modules.get(j));
							modules.remove(j);
						}
					}
				}

				updateLists();

				selectedAdd.clear();
				listModules.clearSelection();
			}

		});

		// The delete button
		buttonDel.addActionListener(new ActionListener() {

			/**
			 * An event called by delete button. Deletes a module from the
			 * addbox.
			 * 
			 * @param actionEvent
			 *            The event.
			 */
			public void actionPerformed(ActionEvent e) {

				for (int i = 0; i < selectedDel.size(); i++) {
					for (int j = 0; j < modulesAdd.size(); j++) {
						if (selectedDel.get(i).equals(
								modulesAdd.get(j).getName())) {
							modules.add(modulesAdd.get(j));
							modulesAdd.remove(j);
						}
					}
				}

				updateLists();

				selectedDel.clear();
				listAddModules.clearSelection();
			}
		});

		// Adds a scroll to the result box
		JScrollPane scrollResult = new JScrollPane(resultBox);
		scrollResult.setPreferredSize(new Dimension(470, 200));

		// Gaps between the components
		layoutBottom.setAutoCreateGaps(true);
		layoutBottom.setAutoCreateContainerGaps(true);

		// Start button and Stop button the same size.
		layoutBottom.linkSize(SwingConstants.HORIZONTAL, buttonStart,
				buttonStop);

		// Sets the layout for the result area.
		// Horizontal.
		layoutBottom
				.setHorizontalGroup(layoutBottom
						.createSequentialGroup()
						.addGroup(
								layoutBottom
										.createParallelGroup(
												GroupLayout.Alignment.CENTER)
										.addComponent(labelResults)
										.addComponent(scrollResult,
												GroupLayout.PREFERRED_SIZE,
												GroupLayout.DEFAULT_SIZE,
												GroupLayout.PREFERRED_SIZE)
										.addGroup(
												layoutBottom
														.createSequentialGroup()
														.addGroup(
																layoutBottom
																		.createParallelGroup(
																				GroupLayout.Alignment.LEADING)
																		.addComponent(
																				buttonStart))
														.addGroup(
																layoutBottom
																		.createParallelGroup(
																				GroupLayout.Alignment.LEADING)
																		.addComponent(
																				buttonStop)))));
		// Vertical.
		layoutBottom.setVerticalGroup(layoutBottom.createSequentialGroup()
				.addComponent(labelResults).addGroup(
						layoutBottom.createParallelGroup(
								GroupLayout.Alignment.BASELINE).addComponent(
								scrollResult, GroupLayout.PREFERRED_SIZE,
								GroupLayout.DEFAULT_SIZE,
								GroupLayout.PREFERRED_SIZE)).addGroup(
						layoutBottom.createParallelGroup(
								GroupLayout.Alignment.LEADING).addComponent(
								buttonStart).addComponent(buttonStop)));

		// The start button
		buttonStart.addActionListener(new ActionListener() {

			/**
			 * An event called by start button. Starts the tests.
			 * 
			 * @param actionEvent
			 *            The event.
			 */
			public void actionPerformed(ActionEvent e) {
				resultBox.setText("This configuration of modules will take "
						+ modulesAdd.size() * 5 + " seconds to complete."
						+ "\nRunning...");
				buttonStop.setEnabled(true);
				starterThread.start();
				buttonStart.setEnabled(false);
				buttonAdd.setEnabled(false);
				buttonDel.setEnabled(false);
				listModules.setEnabled(false);
				listAddModules.setEnabled(false);
			}
		});

		// The stop button
		buttonStop.addActionListener(new ActionListener() {

			/**
			 * An event called by stop button. Stop the tests.
			 * 
			 * @param actionEvent
			 *            The event.
			 */
			public void actionPerformed(ActionEvent e) {
				stopTest();
				buttonStart.setEnabled(false);
				continuing = false;
			}
		});

		// Show the panels with the layout
		add("North", panelTop);
		add("Center", panelBottom);
		setVisible(true);
	}

	/**
	 * Updates the view-lists.
	 */
	private void updateLists() {

		modelModules.clear();
		modelAddModules.clear();

		for (int i = 0; i < modules.size(); i++) {
			modelModules.add(i, modules.get(i).getName());
		}

		for (int i = 0; i < modulesAdd.size(); i++) {
			modelAddModules.add(i, modulesAdd.get(i).getName());
		}

		if (modelAddModules.size() > 0 && continuing == true) {
			buttonStart.setEnabled(true);
		} else {
			buttonStart.setEnabled(false);
		}
	}

	/**
	 * Updates all the results and view it in the text-area.
	 */
	private void updateResult() {

		if (tecpointbenchmark.startup.Main.debug)
			System.out.println("Clearing resultBox...");

		if (tecpointbenchmark.startup.Main.debug)
			System.out.println("Submitting new results to the resultBox");
		
		resultBox.setText("");

		resultBox.append("\n###############");
		resultBox.append("\n### RESULTS ###");
		resultBox.append("\n###############\n\n");
		
		long scoresum = 0;

		// Add new results
		int i = 0;
		for (ResultDTO result : results) {
			if (result.getNumber() == 0) {
				resultBox.append(modelAddModules.get(i) + ":\n");
				resultBox.append(result.getMessage() + "\n");
				resultBox.append("Score: " + result.getPoints() + "\n\n");
				i++;
				scoresum+=result.getPoints();
			} else if (result.getNumber() == 1) {
				resultBox.append("Pressed Stop, no result!");
			} else if (result.getNumber() == 1) {
				resultBox.append("Error, no result!");
			}
		}
		
		resultBox.append("Your total score is: " + scoresum + " pts");
		resultBox.append("\nYour average score is: " + scoresum/results.size() + " pts");

		buttonStop.setEnabled(false);
		if (tecpointbenchmark.startup.Main.debug)
			System.out.println("Clearing the results from array");

		// Remove old results
		results.clear();
	}

	/**
	 * Start all the test that have been chosen by the user.
	 */
	private void startTest() {
		
		// Clear existing text
		resultBox.setText("");

		int moduleCount = modulesAdd.size();
		
		for (ModuleInfoDTO module : modulesAdd) {

			if (continuing == false) {
				break;
			}
			
			resultBox.append("Running module: " + module.getName() + "...");
			cont.runBenchmark(module.getId());

			while (!moduleReady) {
				try {
					if (tecpointbenchmark.startup.Main.debug) {
						System.out.println("moduleReady: " + moduleReady);
					}
					Thread.sleep(500);
				} catch (Exception e) {
					System.out.println(e);
				}
			}

			moduleReady = false;

			if (tecpointbenchmark.startup.Main.debug)
				System.out.println("moduleReady: " + moduleReady);
			
			resultBox.append("\nModule completed!\n");
		}
		
		if (tecpointbenchmark.startup.Main.debug) {
			System.out.println(results.size() + " " + moduleCount);
		}
		
		while(results.size() != moduleCount) {
			try {
				Thread.sleep(50);
			} catch (Exception e) {
				System.out.println(e);
			}
		}
		
		if (tecpointbenchmark.startup.Main.debug) {
			System.out.println("Calling updateResult()");
		}
		
	    updateResult();

	}

	/**
	 * Stop all the test that have been started.
	 */
	private void stopTest() {
		cont.stopBenchmark();
		continuing = false;
		buttonStop.setEnabled(false);
	}

	/**
	 * Updates the resultbox, when all the tests are done.
	 */
	@Override
	public void update(Observable o, Object arg) {

		if (tecpointbenchmark.startup.Main.debug)
			System.out.println("Setting moduleReady to 'True'");

		if (arg instanceof ResultDTO) {
			moduleReady = true;
			results.add((ResultDTO) arg);
		}
	}
}
